package org.jheuristics.ga.multiObjective;

import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.epanetgrid.otimizacao.log.AGLogger;
import org.epanetgrid.otimizacao.replicador.ReplicadorOperator;
import org.epanetgrid.otimizacao.simulation.GradienteNaoConvergeOtimizacaoException;
import org.epanetgrid.otimizacao.simulation.SimulacaoNaoConvergeException;
import org.epanetgrid.otimizacao.util.OtimizacaoUtil;
import org.jheuristics.Individual;
import org.jheuristics.ga.BulkFitnessEvaluator;
import org.jheuristics.ga.FitnessEvaluator;
import org.jheuristics.ga.GAConfig;
import org.jheuristics.ga.GAStatus;
import org.jheuristics.ga.Population;
import org.jheuristics.ga.operators.selectors.NonDominatedSelector;
import org.jheuristics.ga.scalers.RawFitnessScaler;
import org.jheuristics.ga.transformations.DoubleFitnessToDoubleTransformation;
import org.jheuristics.ga.transformations.FitnessToDoubleTransformation;


/**
 * TODO
 *
 * @author Marcell Manfrin, marcell@ourgrid.org, Oct 28, 2005
 */
public class BulkMultiObjetiveFitnessEvaluator implements BulkFitnessEvaluator, Serializable {
    private FitnessEvaluator evaluator;
	private DistanceAlgorithm distanceAlgorithm;
	private int indexDistance;
	private String[] descricoes;

	private ReplicadorOperator replicador;
	
	private AGLogger logger = AGLogger.getInstance();
	
	/**
	 * 
	 * @param evaluator
	 * @param distanceAlgorithm
	 * @param indexDistance
	 * @param replicador
	 */
	public BulkMultiObjetiveFitnessEvaluator(FitnessEvaluator evaluator, DistanceAlgorithm distanceAlgorithm, int indexDistance, 
			ReplicadorOperator replicador) {
		this(evaluator, distanceAlgorithm, indexDistance, null, replicador);
	}
	
	
	/**
	 * @param evaluator
	 * @param distanceAlgorithm
	 * @param indexDistance
	 * @param descricoes
	 * @param replicador
	 */
	public BulkMultiObjetiveFitnessEvaluator(FitnessEvaluator evaluator, DistanceAlgorithm distanceAlgorithm, 
			int indexDistance, String[] descricoes, ReplicadorOperator replicador) {
		setFitnessEvaluator(evaluator);
		setDistanceAlgorithm(distanceAlgorithm);
		setIndexDistance(indexDistance);
		this.descricoes = descricoes;
		this.replicador = replicador;
	}

	/**
	 * TODO
	 *
	 * @param population
	 * @param status
	 * @param config
	 * @see org.jheuristics.ga.BulkFitnessEvaluator#evaluateFitness(org.jheuristics.ga.Population, org.jheuristics.ga.GAStatus, org.jheuristics.ga.GAConfig)
	 */
	public void evaluateFitness(Population population, GAStatus status, GAConfig config) throws GradienteNaoConvergeOtimizacaoException {
		if (0 != population.size()) {
			evaluatingFitness(population, status, config);
			replicador.operate(status, config);
			evaluatingRawAndDistance(population, status, config);
		}
	}

	/**
	 * TODO
	 *
	 * @param population
	 * @param status
	 * @param config
	 */
	protected void evaluatingFitness(Population population, GAStatus status, GAConfig config) throws GradienteNaoConvergeOtimizacaoException {
		MultiObjectiveFitness[] fitness = new MultiObjectiveFitness[population.size()];
		Iterator it = population.iterator();
		
		for (int i = 0; it.hasNext(); i++) {
			Individual individual = (Individual) it.next();
			fitness[i] = (MultiObjectiveFitness) individual.getFitness();
			if (null == fitness[i]) {
				try {
					individual.setFitness(fitness[i] = (MultiObjectiveFitness) getFitnessEvaluator().evaluateFitness(individual, population, status, config));
				}catch (SimulacaoNaoConvergeException e) {
				}
			}
		}
		
	}

	/**
	 * TODO
	 *
	 * @param population
	 * @param status
	 * @param config
	 */
	protected void evaluatingRawAndDistance(Population population, GAStatus status, GAConfig config) {
		
		
		MultiObjectiveFitness[] fitness = new MultiObjectiveFitness[population.size()];
		Iterator it = population.iterator();
		for (int i = 0; it.hasNext(); i++) {
			Individual individual = (Individual) it.next();
			fitness[i] = (MultiObjectiveFitness) individual.getFitness();
		}
		
		
		double[] rawFitness = new RawFitnessScaler().scale(fitness);
		for (int i = 0; i < rawFitness.length; i++) {
			fitness[i].setRawFitness(rawFitness[i]);
		}
		
		Individual[] individuals = population.toIndividualArray();
		
		/*
		 * Logando
		 */
		if(logger.isSelected(AGLogger.DEBUG)) {
			logger.debug("************ Calculo do Strength e do Raw Fitness ************");
			
			StringBuffer buffer = new StringBuffer();
			
			StringBuffer objetivos = new StringBuffer();
			
			if(descricoes != null) {
				for (int i = 0; i < descricoes.length; i++) {
					objetivos.append(descricoes[i]);
					objetivos.append(", ");
				}
			}
			
			
			buffer.append("\nLegenda: ");
			buffer.append(objetivos);
			buffer.append("Ponto de Falha, Severidade do alarme, Strength, Raw Fitness");
			buffer.append("\n");
			
        	int[] strengths = OtimizacaoUtil.calcStrength(individuals);
        	
        	for (int i = 0; i < individuals.length; i++) {
        		Individual individuo = individuals[i];
        		buffer.append(individuo);
        		buffer.append(" ");
        		List dataApl = ((List)individuo.getDataApplication());
        		for (int j = 0; j < dataApl.size(); j++) {
        			if(j < dataApl.size()-1) {
        				buffer.append(dataApl.get(j));
        				buffer.append(" ");
        			}
				}
        		buffer.append(strengths[i]);
        		buffer.append(" ");
        		buffer.append(((MultiObjectiveFitness)individuo.getFitness()).getRawFitness());
        		buffer.append("\n");
        	}
        	logger.debug(buffer);
        	
        	buffer = new StringBuffer();
        	int naoDominados = new NonDominatedSelector().select(population, 0, null, null).length;
        	buffer.append("\nNúmero de não dominados: ");
        	buffer.append(naoDominados);
        	buffer.append("\n");
        	buffer.append("Número de dominados: ");
        	buffer.append(individuals.length - naoDominados);
        	buffer.append("\n");
        	
        	logger.debug(buffer);
        	
        }
		
		/*
		 * Calculando Densidade
		 */
		List[] distances = null;
		if(fitness.length > 1) {
			distances = getDistanceAlgorithm().getDistances(population.toIndividualArray(), status, config);
		}
		if (fitness.length > 1) {
			for (int i = 0; i < distances.length; i++) {
				Collections.sort(distances[i]);
				fitness[i].setDensity(1 / (2 + ((Double) distances[i].get(getIndexDistance())).doubleValue()));
			}
		} else if (fitness.length == 1) {
			fitness[0].setDensity(0.0);
		}
		
		/*
		 * Logando 
		 */
		
		double[][] objNormalizados = OtimizacaoUtil.normalize(individuals);
		if(logger.isSelected(AGLogger.DEBUG)) {
			logger.debug("************ Calculo da densidade e da Aptid�o ************");
			
			StringBuffer objetivos = new StringBuffer();
			
			if(descricoes != null) {
				for (int i = 0; i < descricoes.length; i++) {
					objetivos.append(descricoes[i]);
					objetivos.append(" normalizado");
					objetivos.append(", ");
				}
			}
			
			FitnessToDoubleTransformation transfomations[] = new DoubleFitnessToDoubleTransformation[((MultiObjectiveFitness)individuals[0].getFitness()).getObjectives().length];
			for (int i = 0; i < transfomations.length; i++) {
				transfomations[i] = new DoubleFitnessToDoubleTransformation();
			}
			StringBuffer buffer = new StringBuffer("\n");
			
			buffer.append("\nLegenda: ");
			buffer.append(objetivos);
			buffer.append("Ponto de Falha, Severidade do alarme, M�nima dist�ncia, Densidade, Aptid�o");
			buffer.append("\n");
			
			for (int i = 0; i < objNormalizados.length; i++) {
				buffer.append(individuals[i]);
				buffer.append(" ");
				for (int j = 0; j < objNormalizados[0].length; j++) {
					buffer.append(objNormalizados[i][j]);
					buffer.append(" ");
				}
				
				//adicionar Ponto de Falha, Severidade do Alarme, Minima Distancia, Densidade, Aptidao
				
				List dataApl = (List)individuals[i].getDataApplication();
				
				//falha
				buffer.append(dataApl.get(dataApl.size() - 3));
				buffer.append(" ");
				//severidade
				buffer.append(dataApl.get(dataApl.size() - 2));
				buffer.append(" ");
				//distancia Minima
				if(distances != null) {
					buffer.append(((Double) distances[i].get(getIndexDistance())).doubleValue());
				}else {
					buffer.append("0.0");
				}
				buffer.append(" ");
				//densidade
				buffer.append(fitness[i].getDensity());
				buffer.append(" ");
				//Aptidao
				buffer.append(fitness[i].getDensity() + fitness[i].getRawFitness());
				buffer.append(" ");
				
				buffer.append("\n");
			}
			logger.debug(buffer);
			
		}
		
	}

	/**
	 * TODO
	 *
	 * @return
	 */
	public FitnessEvaluator getFitnessEvaluator() {
		return evaluator;
	}

	/**
	 * TODO
	 *
	 * @param evaluator
	 * @throws NullPointerException
	 */
	public void setFitnessEvaluator(FitnessEvaluator evaluator) {
		if (null == evaluator) {
			// TODO: ERROR MSG
		    throw new NullPointerException();
		}
	    this.evaluator = evaluator;
	}

	/**
	 * TODO
	 *
	 * @return
	 */
	public DistanceAlgorithm getDistanceAlgorithm() {
		return distanceAlgorithm;
	}

	/**
	 * TODO
	 *
	 * @param distanceAlgorithm
	 */
	public void setDistanceAlgorithm(DistanceAlgorithm distanceAlgorithm) {
		if (null == distanceAlgorithm) {
			// TODO: ERROR MSG
			throw new NullPointerException();
		}
		this.distanceAlgorithm = distanceAlgorithm;
	}

	/**
	 * TODO
	 *
	 * @return
	 */
	public int getIndexDistance() {
		return indexDistance;
	}

	/**
	 * TODO
	 *
	 * @param indexDistance
	 */
	public void setIndexDistance(int indexDistance) {
		if (0 > indexDistance) {
			// TODO: ERRO MSG
			throw new IllegalArgumentException();
		}
		this.indexDistance = indexDistance;
	}

}
